require File.dirname(__FILE__) + '/test_helper.rb'

module RSense
  class TestRSense < Test::Unit::TestCase
    def setup
      @ca = CodeAssist.new
    end

    def test_lit
      # TODO more literal
      infer('a=1;b=2.3;c="Hello";d=:Foo')
      assert_lvar(:a, :Fixnum)
      assert_lvar(:b, :Float)
      assert_lvar(:c, :String)
      assert_lvar(:d, :Symbol)
    end

    def test_alias
      infer('def foo() 1 end; alias :bar :foo; a=bar')
      assert_lvar(:a, :Fixnum)
    end
    
    def test_lasgn
      infer('a=1')
      assert_lvar(:a, :Fixnum)
    end
  
    def test_lasgn2
      infer('a=1; a=2.3')
      assert_lvar(:a, :Fixnum, :Float)
    end

    def test_masgn
      infer('a,b=1,2.3')
      assert_lvar(:a, :Fixnum)
      assert_lvar(:b, :Float)
    end

    def test_masgn2
      infer('a,b=[1,2.3]')
      assert_lvar(:a, :Fixnum)
      assert_lvar(:b, :Float)
    end

    def test_masgn3
      infer('a,b=[1,2.3].sort') # method call makes the array value unknown
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_masgn4
      infer('a,b="a".split')
      assert_lvar(:a, :String)
      assert_lvar(:b, :String)
    end
    
    def test_call
      infer('def foo() 1 end; a=foo')
      assert_lvar(:a, :Fixnum)
    end

    def test_call_default_arg
      infer('def foo(n=1) n end; a=foo')
      assert_lvar(:a, :Fixnum)
    end

    def test_call_default_arg2
      infer('def foo(n=1) n end; a=foo(2.3)')
      assert_lvar(:a, :Fixnum, :Float)
    end

    def test_call_args
      infer('def foo(a,*b) b end; x=foo(1,2.3,"Hello")')
      assert_lvar_array(:x, :Float, :String)
    end

    def test_call_splat
      infer('def foo(a,b); b end; a=foo(*[1,2.3])')
      assert_lvar(:a, :Float)
    end

    def test_call_splat2
      infer('def foo(a,b); b end; a=foo("Hello",*[1,2.3])')
      assert_lvar(:a, :Fixnum)
    end

    def test_call_splat3
      infer('def foo(a,b); b end; a=foo(*[1,2.3])')
      assert_lvar(:a, :Float)
    end

    def test_call_splat4
      infer('def foo(a,b); b end; a=foo(*[1,2.3].sort)')
      assert_lvar(:a, :Fixnum, :Float)
    end
  
    def test_numeric_op
      infer('a=1; b=2.3; c=a+b; d=b+a')
      assert_lvar(:a, :Fixnum)
      assert_lvar(:b, :Float)
      assert_lvar(:c, :Float)
      assert_lvar(:d, :Float)
    end
  
    def test_template
      infer('def foo(n) 1+n end; x=1; a=foo(x); x=2.3')
      assert_lvar(:x, :Fixnum, :Float)
      #assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:a, :Fixnum)  # currently lvar is not propagated
    end
  
    def test_module
      infer('module Foo; end; module Foo; def self.foo() 1 end; end; a=Foo.foo')
      assert_lvar(:a, :Fixnum)
    end

    def test_new
      infer('class Foo; end; a=Foo.new')
      assert_lvar(:a, :Foo)
    end

    def test_initialize
      infer('class Foo; def initialize() @a=1 end; def get() @a end; end; a=Foo.new.get')
      assert_lvar(:a, :Fixnum)
    end

    def test_initialize2
      infer('class Foo; def initialize(n) @a=n end; def get() @a end; end; a=Foo.new(1).get')
      assert_lvar(:a, :Fixnum)
    end

    def test_initialize3
      infer('class Foo; def initialize(a,b) @x=b end; def get() @x end; end; a=Foo.new(*[1,2.3].sort).get')
      # TODO
      #assert_lvar(:a, :Fixnum)
    end

    def test_class
      infer('f=1.class;a=f.new')
      assert_lvar(:a, :Fixnum)
      assert_lvar(:f, :Fixnum)
    end

    def test_class2
      infer('class Foo; end; class Foo; def foo() 1 end; end; a=Foo.new.foo')
      assert_lvar(:a, :Fixnum)
    end

    def test_attr
      infer('class Foo; def initialize() @a=0 end; attr :a; end; a=Foo.new.a')
      assert_lvar(:a, :Fixnum)
    end

    def test_attr_reader
      infer('class Foo; def initialize() @a=0 end; attr_reader :a; end; a=Foo.new.a')
      assert_lvar(:a, :Fixnum)
    end

    def test_attr_writer
      infer('class Foo; attr_reader :a; attr_writer :a; end; f=Foo.new; f.a=0; a=f.a')
      assert_lvar(:a, :Fixnum)
    end

    def test_attr_accessor
      infer('class Foo; attr_accessor :a; end; f=Foo.new; f.a=0; a=f.a')
      assert_lvar(:a, :Fixnum)
    end

    def test_const
      infer('class Foo; A=1; end; a=Foo::A')
      assert_lvar(:a, :Fixnum)
    end

    def test_const2
      infer('module Foo; A=1; end; a=Foo::A')
      assert_lvar(:a, :Fixnum)
    end

    def test_const3
      infer('class Foo; A=1; end; a=Foo; b=a::A')
      assert_lvar(:a, :Foo)
      assert_lvar(:b, :Fixnum)
    end

    def test_const4
      infer('module Foo; A=1; end; module Bar; include Foo; end;a=Bar::A')
      assert_lvar(:a, :Fixnum)
    end

    def test_class_method
      infer('class Foo; def Foo.bar() 1 end; end; a=Foo.bar')
      assert_lvar(:a, :Fixnum)
    end

    def test_class_method2
      infer('class Foo; def self.bar() 1 end; end; a=Foo.bar')
      assert_lvar(:a, :Fixnum)
    end

    def test_class_method3
      infer('class Foo; A=1; def self.bar() A end; end; a=Foo.bar')
      assert_lvar(:a, :Fixnum)
    end
  
    def test_method
      infer('class Foo; def bar() 1 end; end; a=Foo.new.bar')
      assert_lvar(:a, :Fixnum)
    end

    def test_super_method
      infer('class Foo; def foo() 1 end; end; class Bar < Foo; def foo() super() + 2.3; end; end; a=Bar.new.foo')
      assert_lvar(:a, :Float)
    end

    def test_super_method2
      infer('class Foo; def self.new() super() end; end; a=Foo.new')
      assert_lvar(:a, :Foo)
    end

    def test_super_method3
      infer('class Foo; def foo(n) n end; end; class Bar < Foo; def foo(n) super; end; end; a=Bar.new.foo(1)')
      assert_lvar(:a, :Fixnum)
    end

    def test_super_method4
      infer('class Foo; def self.new() 0 end; end; class Bar < Foo; def self.new() super() end; end; a=Bar.new')
      assert_lvar(:a, :Fixnum)
    end

    def test_recursive_call
      infer('def fact(n) n == 0 ? 1 : n * fact(n-1) end; a=fact(10)')
      assert_lvar(:a, :Fixnum)
    end

    def test_array
      infer('a=[1]; b=a[0]')
      assert_lvar(:b, :Fixnum)
    end

    def test_array_push
      infer('a=[]; a<<1; a<<"Hello"; b=a[0]')
      assert_lvar(:b, :Fixnum, :String)
    end

    def test_array_push2
      # TODO
      #infer('a=[1]; b=a[0]; a<<"Hello"')
      #assert_lvar(:b, :Fixnum, :String)
    end

    def test_hash
      infer('a={:x=>1}; b=a.keys; c=a.values')
      assert_lvar_array(:b, :Symbol)
      assert_lvar_array(:c, :Fixnum)
    end

    def test_hash_each
      infer('a={:x=>1}; a.each{|k,v|}')
      assert_lvar(:k, :Symbol)
      assert_lvar(:v, :Fixnum)
    end

    def test_all?
      infer('a=[1].all?{|v|}')
      assert_lvar(:v, :Fixnum)
      assert_lvar_boolean(:a)
    end

    def test_any?
      infer('a=[1].any?{|v|}')
      assert_lvar(:v, :Fixnum)
      assert_lvar_boolean(:a)
    end

    def test_map
      infer('a=[1].map{|i| i.to_s}[0]')
      assert_lvar(:a, :String)
    end

    def test_collect
      infer('a=[1].collect{|i| i.to_s}[0]')
      assert_lvar(:a, :String)
    end

    def test_each_with_index
      infer('[2.3].each_with_index{|v,i|}')
      assert_lvar(:v, :Float)
      assert_lvar(:i, :Fixnum)
    end

    def test_find
      infer('a=[1,2.3].find{|v|}')
      assert_lvar(:a, :Fixnum, :Float, :NilClass)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_detect
      infer('a=[1,2.3].detect{|v|}')
      assert_lvar(:a, :Fixnum, :Float, :NilClass)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_find_all
      infer('a=[1,2.3].find_all{|v|}')
      assert_lvar_array(:a, :Fixnum, :Float)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_select
      infer('a=[1,2.3].select{|v|}')
      assert_lvar_array(:a, :Fixnum, :Float)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_grep
      infer('a=[1,2.3].grep{|v|}')
      assert_lvar_array(:a, :Fixnum, :Float)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_inject
      infer('a=[1,2,3].inject{|r,v|r+v}')
      assert_lvar(:a, :Fixnum, :NilClass)
      assert_lvar(:r, :Fixnum, :NilClass)
    end

    def test_max
      infer('x=[1,2.3].max{|a, b|}')
      assert_lvar(:x, :Fixnum, :Float, :NilClass)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_max_by
      infer('x=[1,2.3].max_by{|a, b|}')
      assert_lvar(:x, :Fixnum, :Float, :NilClass)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_min
      infer('x=[1,2.3].min{|a, b|}')
      assert_lvar(:x, :Fixnum, :Float, :NilClass)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_min_by
      infer('x=[1,2.3].min_by{|a, b|}')
      assert_lvar(:x, :Fixnum, :Float, :NilClass)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_partition
      infer('a=[1,2.3].partition{|v|}; b=a[0]')
      assert_lvar_array(:b, :Fixnum, :Float)
    end

    def test_reject
      infer('a=[1,2.3].reject{|v|}')
      assert_lvar_array(:a, :Fixnum, :Float)
      assert_lvar(:v, :Fixnum, :Float)
    end

    def test_sort
      infer('x=[1,2.3].sort{|a,b|}')
      assert_lvar_array(:x, :Fixnum, :Float)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_sort_by
      infer('x=[1,2.3].sort_by{|a,b|}')
      assert_lvar_array(:x, :Fixnum, :Float)
      assert_lvar(:a, :Fixnum, :Float)
      assert_lvar(:b, :Fixnum, :Float)
    end

    def test_to_a
      infer('a=[1,2.3].to_a')
      assert_lvar_array(:a, :Fixnum, :Float)
    end

    def test_entries
      infer('a=[1,2.3].entries')
      assert_lvar_array(:a, :Fixnum, :Float)
    end

    def test_ivar
      infer('class Foo
               def set() @x=1 end
               def get() @x end
             end
             a=Foo.new
             a.set
             b=a.get')
      assert_lvar(:a, :Foo)
      assert_lvar(:b, :Fixnum)
    end

    def test_ivar2
      infer('class Foo
               def set(x) @x=x end
               def get() @x end
             end
             a=Foo.new
             a.set(1)
             b=Foo.new
             b.set(2.3)
             b.set("Hello")
             c=a.get
             d=b.get')
      assert_lvar(:a, :Foo)
      assert_lvar(:b, :Foo)
      assert_lvar(:c, :Fixnum)
      assert_lvar(:d, :Float, :String)
    end

    def test_data_poly
      infer('class C1; def foo() 1 end; end
             class C2; def foo() 2.3 end; end
             class C3; def foo() "Hello" end; end
             def inst() return C1.new; return C2.new; return C3.new; end
             a=inst; b=a.foo')
      assert_lvar(:a, :C1, :C2, :C3)
      assert_lvar(:b, :Fixnum, :Float, :String)
    end

    def infer(s) @ca.infer(s) end
    
    def assert_lvar(name, *types)
      v = @ca.local_var(name)
      assert_not_nil(v)
      ts1 = Set.new
      v.type_var.each {|t| ts1 << t.to_s}
      ts2 = Set.new
      types.each {|t| ts2 << t.to_s}
      assert_equal(ts2, ts1)
    end

    def assert_lvar_array(name, *types)
      v = @ca.local_var(name)
      assert_not_nil(v)
      ts1 = Set.new
      assert(v.type.respond_to?(:elem_type_var), 'variable is not array')
      v.type.elem_type_var.each {|t| ts1 << t.to_s}
      ts2 = Set.new
      types.each {|t| ts2 << t.to_s}
      assert_equal(ts2, ts1)
    end

    def assert_lvar_boolean(name)
      assert_lvar(name, :TrueClass, :FalseClass)
    end
  
    def test_suggest_new
      result = suggest('Object.', 7)
      assert_include(result, :new)
    end

    def test_suggest_new2
      result = suggest('1.class.', 8)
      assert_include(result, :new)
    end

    def test_suggest_fixnum
      result = suggest('1.', 2)
      assert_include(result, :next)
    end

    def test_suggest_method
      result = suggest('def foo(n) n. end; foo(1)', 13)
      assert_include(result, :next)
    end

    def test_suggest_map
      result = suggest('[1].map{|v|v}.', 14)
      assert_include(result, :map)
    end

    def test_suggest_map2
      result = suggest('[1].map{|v|v}.map{|z|z}.', 24)
      assert_include(result, :map)
    end

    def test_suggest_map3
      result = suggest('[1].map{|v|v}.map{|z|z.}', 23)
      assert_include(result, :next)
    end

    def test_suggest_ivar
      result = suggest('class Foo; def initialize(n) @n = n end; def get() @n. end; end; Foo.new("a").get', 54)
      assert_include(result, :gsub)
      # do twice
      suggest('class Foo; def initialize(n) @n = n end; def get() @n. end; end; Foo.new("a").get', 54)
      assert_include(result, :gsub)
    end

    def test_suggest_colon2
      result = suggest('Object::', 8)
      assert_include(result, :Fixnum)
    end
  
    def suggest(source, point)
      @ca.suggest('', source, point)
    end
  
    def assert_include(result, *expected)
      expected.each do |e|
        assert(result.candidates.include?(e), "`#{e}' is not included in #{result.candidates.inspect}.")
      end
    end
  end
end
